// Camera
float3 g_vCameraWorldPosition;
float4x4 g_mWorld;
float4x4 g_mView;
float4x4 g_mProjection;

// Lights
#define LIGHT_COUNT 4
float4 g_vLightPosition[LIGHT_COUNT];
float4 g_vLightDiffuse[LIGHT_COUNT];
float4 g_vLightSpecular[LIGHT_COUNT];
float4 g_vLightAmbient;

// Textures
texture g_Texture;
texture g_IlluminationMap;

// Samplers
sampler g_Sampler = sampler_state
{
    Texture = g_Texture;
	AddressU = Wrap;
	AddressV = Wrap;
};

sampler g_IlluminationSampler = sampler_state
{
    Texture = g_IlluminationMap;
	AddressU = Wrap;
	AddressV = Wrap;
};

struct VertexShaderInput
{
    float4 vPosition : POSITION0;
	float3 vNormal : NORMAL0;
    float2 vTextureCoordinate : TEXCOORD0;
};

struct VertexShaderOutput
{
    float4 vPosition : POSITION0;
	float4 vWorldPosition : TEXCOORD1;
	float3 vNormal : NORMAL0;
    float2 vTextureCoordinate : TEXCOORD0;
	float fDepth : DEPTH0;
};

struct DepthVertexShaderOutput
{
    float4 vPosition : POSITION0;
	float fDepth : DEPTH0;
};

DepthVertexShaderOutput DepthVertexShader(VertexShaderInput _Input, float4x4 _mTransform : BLENDWEIGHT)
{
	DepthVertexShaderOutput Output;

	// Transform the transform into world space
	_mTransform = mul(g_mWorld, transpose(_mTransform));

	// Transform the vertex position into view space
	Output.vPosition = mul(_Input.vPosition, _mTransform);
	Output.vPosition = mul(Output.vPosition, g_mView);

	// Calculate the depth and scale the range
	Output.fDepth = -Output.vPosition.z / 2000.0f;

	// Transform the vertex position into screen space
	Output.vPosition = mul(Output.vPosition, g_mProjection);

	return Output;
}

float4 DepthPixelShader(DepthVertexShaderOutput _Input) : COLOR0
{
	return float4(_Input.fDepth, _Input.fDepth, _Input.fDepth, _Input.fDepth);
}

VertexShaderOutput DefaultVertexShader(VertexShaderInput _Input, float4x4 _mTransform)
{
	// Create the output structure
    VertexShaderOutput Output;

	// Transform the vertex position into world space
	Output.vWorldPosition = mul(_Input.vPosition, _mTransform);

	// Transform the normal into world space
	Output.vNormal = normalize(mul(_Input.vNormal, _mTransform));

	// Apply the view matrix to the vertex
	Output.vPosition = mul(Output.vWorldPosition, g_mView);

	// Calculate the depth and scale the range
	Output.fDepth = -Output.vPosition.z / 2000.0f;

	// Apply the projection matrix to the vertex
	Output.vPosition = mul(Output.vPosition, g_mProjection);

	// Copy the texture coordinates
    Output.vTextureCoordinate = _Input.vTextureCoordinate;

	// Return the output structure
    return Output;
}


VertexShaderOutput InstancingVertexShader(VertexShaderInput _Input,
                                                  float4x4 _mTransform : BLENDWEIGHT)
{
    return DefaultVertexShader(_Input, mul(g_mWorld, transpose(_mTransform)));

}

float4 DefaultPixelShader(VertexShaderOutput _Input) : COLOR0
{
	float4 vColor = g_vLightAmbient;
	float4 vSpecularColor = float4(0.0, 0.0, 0.0, 0.0);

	// For each light
	for(int iLight = 0; iLight < LIGHT_COUNT; ++iLight)
	{
		// Add the diffuse lighting
		float3 vToLight = normalize(g_vLightPosition[iLight] - _Input.vWorldPosition);
		float fDiffuseAmount = dot(_Input.vNormal, vToLight);
		vColor += saturate(mul(g_vLightDiffuse[iLight], fDiffuseAmount));

		// Calculate the reflection vector for specular lighting
		float3 vReflection = 2.0 * dot(vToLight, _Input.vNormal) * _Input.vNormal - vToLight;
		vReflection = normalize(vReflection);

		// Calculate the vector to the camera
		float3 vToCamera = normalize(g_vCameraWorldPosition - _Input.vWorldPosition);

		// Add the specular lighting
		float fSpecularAmount = saturate(dot(vToCamera, vReflection));
		fSpecularAmount = pow(fSpecularAmount, 128);
		vSpecularColor += saturate(mul(g_vLightSpecular[iLight], fSpecularAmount));
	}

	// Multiply the color by the texture lookup result, add the specular color, and set the depth
	vColor = vColor * tex2D(g_Sampler, _Input.vTextureCoordinate) + vSpecularColor;
	vColor.a = _Input.fDepth;
	return vColor;
}

float4 IlluminationPixelShader(VertexShaderOutput _Input) : COLOR0
{
	float4 vColor = g_vLightAmbient;
	float4 vSpecularColor = float4(0.0, 0.0, 0.0, 0.0);

	// For each light
	for(int iLight = 0; iLight < LIGHT_COUNT; ++iLight)
	{
		// Add the diffuse lighting
		float3 vToLight = normalize(g_vLightPosition[iLight] - _Input.vWorldPosition);
		float fDiffuseAmount = dot(_Input.vNormal, vToLight);
		vColor += saturate(mul(g_vLightDiffuse[iLight], fDiffuseAmount));

		// Calculate the reflection vector for specular lighting
		float3 vReflection = 2.0 * dot(vToLight, _Input.vNormal) * _Input.vNormal - vToLight;
		vReflection = normalize(vReflection);

		// Calculate the vector to the camera
		float3 vToCamera = normalize(g_vCameraWorldPosition - _Input.vWorldPosition);

		// Add the specular lighting
		float fSpecularAmount = saturate(dot(vToCamera, vReflection));
		fSpecularAmount = pow(fSpecularAmount, 128);
		vSpecularColor += saturate(mul(g_vLightSpecular[iLight], fSpecularAmount));
	}

	vColor = max(vColor, tex2D(g_IlluminationSampler, _Input.vTextureCoordinate));

	// Multiply the color by the texture lookup result, add the specular color, and set the depth
	vColor = vColor * tex2D(g_Sampler, _Input.vTextureCoordinate) + vSpecularColor;
	vColor.a = _Input.fDepth;
	return vColor;
}

float4 CelestialBodiesPixelShader(VertexShaderOutput _Input) : COLOR0
{
	float4 vColor = g_vLightAmbient;
	float4 vSpecularColor = float4(0.0, 0.0, 0.0, 0.0);

	// For each light
	for(int iLight = 0; iLight < LIGHT_COUNT; ++iLight)
	{
		// Add the diffuse lighting
		float3 vToLight = normalize(g_vLightPosition[iLight] - _Input.vWorldPosition);
		float fDiffuseAmount = dot(_Input.vNormal, vToLight);
		vColor += saturate(mul(g_vLightDiffuse[iLight], fDiffuseAmount));

		// Calculate the reflection vector for specular lighting
		float3 vReflection = 2.0 * dot(vToLight, _Input.vNormal) * _Input.vNormal - vToLight;
		vReflection = normalize(vReflection);

		// Calculate the vector to the camera
		float3 vToCamera = normalize(g_vCameraWorldPosition - _Input.vWorldPosition);

		// Add the specular lighting
		float fSpecularAmount = saturate(dot(vToCamera, vReflection));
		fSpecularAmount = pow(fSpecularAmount, 64);
		vSpecularColor += saturate(mul(g_vLightSpecular[iLight], fSpecularAmount));
	}

	// Calculate diffuse color
	float4 vTextureColor = tex2D(g_Sampler, _Input.vTextureCoordinate);

	// If this is not water
	if(vTextureColor.b < vTextureColor.r * 2.0f)
	{
		// Scale down the specular highlight
		vSpecularColor *= 0.2f;
	}

	// Multiply the color by the texture lookup result, add the specular color, and set the depth
	vColor = vColor * vTextureColor + vSpecularColor;
	vColor.a = _Input.fDepth;
	return vColor;
}

float4 HUD3DPixelShader(VertexShaderOutput _Input) : COLOR0
{
	return tex2D(g_Sampler, _Input.vTextureCoordinate);
}

// Render things
technique Instancing
{
    pass Pass1
    {
        VertexShader = compile vs_3_0 InstancingVertexShader();
        PixelShader = compile ps_3_0 DefaultPixelShader();
    }
}

technique Illumination_Instancing
{
    pass Pass1
    {
        VertexShader = compile vs_3_0 InstancingVertexShader();
        PixelShader = compile ps_3_0 IlluminationPixelShader();
    }
}

technique Celestial_Body_Instancing
{
    pass Pass1
    {
        VertexShader = compile vs_3_0 InstancingVertexShader();
        PixelShader = compile ps_3_0 CelestialBodiesPixelShader();
    }
}

technique HUD_3D_Instancing
{
    pass Pass1
    {
        VertexShader = compile vs_3_0 InstancingVertexShader();
        PixelShader = compile ps_3_0 HUD3DPixelShader();
    }
}

technique Depth_Instancing
{
    pass Pass1
    {
        VertexShader = compile vs_3_0 DepthVertexShader();
        PixelShader = compile ps_3_0 DepthPixelShader();
    }
}